package logo;

import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

interface ParseCommand
{
	public int parse(List<String> _input, int i);
	public ICommand getCommand();
}

class ParseUp implements ParseCommand
{
	@Override
	public int parse(List<String> _input, int i) {
		return 0;
	}

	@Override
	public ICommand getCommand() {
		return new CommandUp();
	}
}

class ParseDown implements ParseCommand
{
	@Override
	public int parse(List<String> _input, int i) {
		return 0;
	}

	@Override
	public ICommand getCommand() {
		// TODO Auto-generated method stub
		return new CommandDown();
	}
}

class ParseForward implements ParseCommand
{
	String _length;
	
	@Override
	public int parse(List<String> _input, int i) {
		_length = _input.get(i);
		return 1;	// 1 parameter needed
	}

	@Override
	public ICommand getCommand() {
		try
		{
			return new CommandForward(Integer.valueOf(_length));
		}
		catch(NumberFormatException ex)
		{
			return new CommandForward(_length);
		}
	}
	
}

class ParseLeft implements ParseCommand
{
	String _degrees;
	
	@Override
	public int parse(List<String> _input, int i) {
		_degrees = _input.get(i);
		return 1;	// 1 parameter needed
	}

	@Override
	public ICommand getCommand() {
		try
		{
			return new CommandLeft(Integer.valueOf(_degrees));
		}
		catch(NumberFormatException ex)
		{
			return new CommandLeft(_degrees);
		}
	}
}

class ParseRight implements ParseCommand
{
	String _degrees;
	@Override
	public int parse(List<String> _input, int i) {
		_degrees = _input.get(i);
		return 1;	// 1 parameter needed
	}
	@Override
	public ICommand getCommand() {
		return new CommandRight(Integer.valueOf(_degrees));
	}
}

class ParseReset implements ParseCommand
{
	@Override
	public int parse(List<String> _input, int i) {
		return 0;
	}

	@Override
	public ICommand getCommand() {
		return new CommandReset();
	}
	
}

class ParseLoop implements ParseCommand
{
	CommandLoop _loopcmd;

	@Override
	public int parse(List<String> _input, int i) {
		
		int index = i;
		Debug.log("loopcount = " + _input.get(index));
		int loopcount = Integer.valueOf(_input.get(index));
		++index;
		
		_loopcmd = new CommandLoop(loopcount);
		
		while (!_input.get(index).toUpperCase().equals("endloop".toUpperCase()))
		{
			String token = _input.get(index);
			++index;
			Debug.log("Loop contains: " + token);
			ParseCommand pc = Parser.getParsers().getParser(token);
			index += pc.parse(_input, index);
			_loopcmd.addSubCommand(pc.getCommand());
		}
		return index - i + 1;
	}

	@Override
	public ICommand getCommand() {
		return _loopcmd;
	}
}

class ParseFunction implements ParseCommand
{

	CommandFunction _functioncmd;
	
	@Override
	public int parse(List<String> _input, int i) {
		int index = i;
		Debug.log("Function: " + _input.get(index));
		_functioncmd = new CommandFunction(_input.get(index));
		++index;

		while (!_input.get(index).toUpperCase().equals("endfunction".toUpperCase()))
		{
			String token = _input.get(index);
			++index;
			Debug.log("function contains: " + token);
			ParseCommand pc = Parser.getParsers().getParser(token);
			index += pc.parse(_input, index);
			_functioncmd.addCommand(pc.getCommand());
		}
		
		return index - i + 1;
	}

	@Override
	public ICommand getCommand() {
		return _functioncmd;
	}
	
}

class ParseCall implements ParseCommand
{
	CommandCall _callcmd;

	@Override
	public int parse(List<String> _input, int i) {
		_callcmd = new CommandCall("");
		int index = i;
		Debug.log("Call: " + _input.get(index));
		_callcmd = new CommandCall(_input.get(index));
		++index;

		String token = _input.get(index);
		++index;
		Debug.log("call contains: " + token);
		if (token.equals("("))
		{
			// build parameter list
			while (!token.equals(")"))
			{
				String name = _input.get(index++);
				if (!_input.get(index++).equals(":"))
				{
					Debug.error("Expecting ':' near " + name);
				}
				String value = _input.get(index++);
				Debug.log("parameter: " + name + "=" + value);
				try { _callcmd.addParameter(name, Integer.valueOf(value)); }
				catch (NumberFormatException ex)
				{
					Debug.error("Expecting integer value, but got " + value);
				}
				token = _input.get(index);
			}
		}
		return index - i + 1;

	}

	@Override
	public ICommand getCommand() {
		return _callcmd;
	}
}

class ParseSeparator implements ParseCommand
{

	@Override
	public int parse(List<String> _input, int i) {
		return 0;
	}

	@Override
	public ICommand getCommand() {
		return null;
	}
}

class ParsePair
{
	String _command;
	ParseCommand _parser;
	
	public ParsePair(String cmd, ParseCommand parser)
	{
		_command = cmd;
		_parser = parser;
	}
}

class ParsePairs extends Vector<ParsePair>
{
	private static final long serialVersionUID = 1L;

	public ParseCommand getParser(String key)
	{
		for (ParsePair pp: this)
		{
			if (pp._command.toUpperCase().equals(key.toUpperCase()))
			{
				return pp._parser;
			}
		}
		return null;
	}
}

class Parser {

	Commands _commands;
	static ParsePairs _parsers;
	List<String> _input;

	private void buildParsePairs()
	{
		final String[] keywords = {			"forward", "up", "down", "left", "right",
											"reset", "loop", "function", "call", ";"};
		final ParseCommand[] parsers = {	new ParseForward(), new ParseUp(), new ParseDown(), new ParseLeft(), new ParseRight(), 
											new ParseReset(), new ParseLoop(), new ParseFunction(), new ParseCall(), new ParseSeparator()};
		
		assert(keywords.length == parsers.length);
		
		for (int i = 0; i < keywords.length; ++i)
		{
			ParsePair pp = new ParsePair(keywords[i], parsers[i]);
			_parsers.add(pp);
		}
		
	}
	
	static ParsePairs getParsers()
	{
		return Parser._parsers;
	}
	
	private List<String> removeWhitespace(List<String> input)
	{
		List<String> result = new ArrayList<String>();

		for (String line: input)
		{
			String[] explode = line.split("[\\s]");
			
			for (String word: explode)
				if (word.length() > 0)
					result.add(word);
		}
		return result;
	}
	
	private List<String> tokenize(List<String> input)
	{
		List<String> result = new ArrayList<String>();
		final String[] tokens = { "[", "]", "(", ")", "\"", "'", ";", ":" };

		input = removeWhitespace(input);
		
		for (String word: input)
		{
			int firsttokenappearance = 0;
			String foundToken = null;

			do
			{
				firsttokenappearance = Integer.MAX_VALUE;
				
				for (String token: tokens)
				{
					if (word.indexOf(token) != -1)
					{
						if (word.indexOf(token) < firsttokenappearance)
						{
							firsttokenappearance = word.indexOf(token);
							foundToken = token;
						}
					}
				}
				
				if (firsttokenappearance != Integer.MAX_VALUE)
				{
					if (firsttokenappearance == 0)
					{
						result.add(foundToken);
						word = word.substring(1);
					}
					else
					{
						result.add(word.substring(0, firsttokenappearance));
						word = word.substring(firsttokenappearance);
					}
				}
			} while (firsttokenappearance != Integer.MAX_VALUE);
			
			if (word.length() > 0)
				result.add(word);
		}
		return result;
	}
	
	public Parser(List<String> list)
	{
		_commands = new Commands();
		_input = new ArrayList<String>();
		_parsers = new ParsePairs();
		_input = tokenize(list);
		buildParsePairs();
		parseText();
	}
	
	
	public void parseText()
	{
		for (int i = 0; i < _input.size();)
		{
			String token = _input.get(i);
			++i;
			ParseCommand pc = _parsers.getParser(token);
			
			if (pc == null)
			{
				Debug.error("Unknown token " + token);
				return;
			}
			
			if (pc instanceof ParseSeparator)
				continue;

			i += pc.parse(_input, i);
			
			Debug.log(pc.getCommand().toString());
			
			_commands.add(pc.getCommand());
		}
	}
	
	public void testparseText() {
		// this will be permanent, even with the file parser
		CommandFunction main = new CommandFunction("main");
		CommandCall cmain = new CommandCall("main");
		_commands.addCommand(main);
		_commands.addCommand(cmain);
		
		// this part would be from the file parser (or manual input, ...)
		main.addCommand(new CommandDown());
		
		CommandLoop l1 = new CommandLoop(4);
		l1.addSubCommand(new CommandForward(50));
		l1.addSubCommand(new CommandLeft(90));
		main.addCommand(l1);

		CommandLoop l2 = new CommandLoop(90);
		l2.addSubCommand(new CommandForward(8));
		l2.addSubCommand(new CommandRight(4));
		main.addCommand(l2);
		
		main.addCommand(new CommandWait(1000));
		main.addCommand(new CommandReset());
		
		CommandFunction f1 = new CommandFunction("drawsquare");
		f1.addCommand(new CommandForward("length"));
		f1.addCommand(new CommandRight(90));
		f1.addCommand(new CommandForward("length"));
		f1.addCommand(new CommandRight(90));
		f1.addCommand(new CommandForward("length"));
		f1.addCommand(new CommandRight(90));
		f1.addCommand(new CommandForward("length"));
		f1.addCommand(new CommandRight(90));
		main.addCommand(f1);
		
		CommandCall c1 = new CommandCall("drawsquare");
		c1.addParameter("length", 100);
		main.addCommand(c1);
	}

	public Commands getCommands() {
		return _commands;
	}
	
}
